一、理解对象
1.创建一个对象,然后给这个对象新建属性和方法。
①常见的创建方式
var person = new Object(); //创建一个Object 对象person.name = 'XIE'; //创建一个name 属性并赋值person.age = 20; //创建一个age 属性并赋值person.sayName = function () { //创建一个run()方法并返回值return this.name; //this指的是person本身};
②字面量的形式
var person = {name:"xie",age:20,job:"学生",sayName:function(){return this.name;}
}
2.数据属性
数据属性有4个描述其行为的特性。
特性 | 默认值 | 含义 |
---|---|---|
configurable | true | 是否可配置,表示能否删除属性重新定义,能否修改属性的特性,能否修改为访问器属性。 |
enumerable | true | 是否可枚举 |
writable | true | 是否可修改属性值 |
value | undefined | 属性的数据值 |
注:configurable特性一旦设置为false后,则不能再变为true.
var person={};
Object.defineProperty(person,"name",{configurable:true,enumerable:true,writable:true,value:"xie"
})
3.访问器属性
特性 | 默认值 | 含义 |
---|---|---|
configurable | true | 是否可配置,表示能否删除属性重新定义,能否修改属性的特性,能否修改为访问器属性。 |
enumerable | true | 是否可枚举 |
get | undefined | 读取属性时调用 |
set | undefined | 写入属性时调用 |
二、创建对象
1.工厂模式
function createObject(name, age) { //集中实例化的函数var obj = new Object();obj.name = name;obj.age = age;obj.run = function () {return this.name + this.age + '运行中...';};return obj;
}
var box1 = createObject('Lee', 100); //第一个实例
var box2 = createObject('Jack', 200); //第二个实例
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法弄清楚一个对象的类型。
2.构造函数模式
function Person(name,age){this.name = name;this.age = age;this.run = function(){alert(this.name);}
}
var person1 = new Person("Lee",100);
var person2 = new Person("Jack",200);
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
- 构造函数方法没有显示的创建对象(new Object());
- 直接将属性和方法赋值给this 对象;
- 没有renturn 语句。
构造函数的方法有一些规范:
- .函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和普通函数);
- 通过构造函数创建对象,必须使用new 运算符。
使用new操作符创建实例时,执行过程如下:
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(因此this就指向了这个对象);
- 执行构造函数中的代码(为这个新对象添加属性);
返回新对象
使用构造函数的主要问题,就是每个方法都要再每个实例上重新创建一遍。通过下面可知不一样:
alert(person1.run == person2.run); //false
3.原型模式
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型的好处可以让所有对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
3.1常见的语法
// 原型模式
function Box() {} //声明一个构造函数
Box.prototype.name = 'Lee'; //在原型里添加属性
Box.prototype.age = 100;
Box.prototype.run = function () { //在原型里添加方法return this.name + this.age + '运行中...';
};
//比较一下原型内的方法地址是否一致:
var box1 = new Box();
var box2 = new Box();
alert(box1.run == box2.run); //true,方法的引用地址保持一致
为
下面可通过图进一步了解
在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。proto属性是实例指向原型对象的一个指针,ECMAScript5称这个指针为[[Prototype]],它的作用就是指向构造函数的原型属性constructor。
isPrototypeOf()方法来判断一个对象是否指向了该构造函数的原型
alert(Box.prototype.isPrototypeOf(box1)); //true,只要实例化对象,即都会指向
Object.getPrototype()方法返回[[Prototype]]的值。IE9+,Firefox3.5+,Safari5+,Opera12+ 和Chrome支持
alert(Object.getPrototype(box1).name); //Lee
原型模式的执行流程:
- .先查找构造函数实例里的属性或方法,如果有,立刻返回;
- 如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
hasOwnProperty()函数判断构造函数的实例中是否有该属性。
alert(box.hasOwnProperty('name')); //实例里有返回true,否则返回false
in操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中。
alert('name' in box); //true,存在实例中或原型中
function isProperty(object, property) { //判断原型中是否存在属性return !object.hasOwnProperty(property) && (property in object);
}
3.2更简单的原型语法
为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式:
function Box() {};
Box.prototype = { //使用字面量的方式name : 'Lee',age : 100,run : function () {return this.name + this.age + '运行中...';}
};
使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式使用constructor 属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
var box = new Box();
alert(box instanceof Box); //true
alert(box instanceof Object); //true
alert(box.constructor == Box); //字面量方式,返回false,否则,true
alert(box.constructor == Object); //字面量方式,返回true,否则,false
如果想让字面量方式的constructor 指向实例对象,可以如下所示
function Box() {};
Box.prototype = { //使用字面量的方式constructor : Box, //直接强制指向即可name : 'Lee',age : 100,run : function () {return this.name + this.age + '运行中...';}
};
重设constructor会导致它的enumerable特性被修改为true,默认情况下,原生的constructor是不可枚举的,可使用Object.defineProperty()方法重设构造函数。
Object.defineProperty(Box.prototype,"constructor",{enumerable:false,value:Box
});
原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。
function Box() {};
Box.prototype = { //原型被重写了constructor : Box,name : 'Lee',age : 100,run : function () {return this.name + this.age + '运行中...';}
};
Box.prototype = {age = 200
};
var box = new Box(); //在这里声明
alert(box.run()); //erroe,因为box 只是最初声明的原型
3.3原型对象的问题
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:
function Box() {};
Box.prototype = {constructor : Box,name : 'Lee',age : 100,family : ['父亲', '母亲', '妹妹'], //添加了一个数组属性run : function () {return this.name + this.age + this.family;}
};
var box1 = new Box();
box1.family.push('哥哥'); //在实例中添加'哥哥'
alert(box1.run());
var box2 = new Box();
alert(box2.run()); //共享带来的麻烦,也有'哥哥'了
4.动态原型模式
为了解决构造传参和共享问题,可以组合构造函数+原型模式,即动态原型模式。
function Box(name ,age) { //将所有信息封装到函数体内this.name = name; //不共享的使用构造函数形式this.age = age;if (typeof this.run != 'function') { //仅在第一次调用的初始化Box.prototype.run = function () { //共享的使用原型模式return this.name + this.age + '运行中...';};}
}
var box = new Box('Lee', 100);
alert(box.run());
当第一次调用构造函数时,run()方法发现不存在,然后初始化原型。当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会再初始化了。这样及得到了封装,又实现了原型方法共享,并且属性都保持独立。
PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系。
5.寄生构造函数模式
寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系。
function myString(string) {var str = new String(string);str.addstring = function () {return this + ',被添加了!';};return str;
}
var box = new myString('Lee'); //比直接在引用原型添加要繁琐好多
alert(box.addstring());
使用场景:要创建一个具有额外方法的引用类型时。
6.稳妥构造函数模式
在一些安全的环境中,比如禁止使用this 和new,这里的this 是构造函数里不使用this,这里的new 是在外部实例化构造函数时不使用new。
function Box(name , age) {var obj = new Object();obj.run = function () {return name + age + '运行中...'; //直接打印参数即可};return obj;
}
var box = Box('Lee', 100); //直接调用函数
alert(box.run());
PS:稳妥构造函数和寄生类似。