JavaScript创建对象的八种方式
1.字面量形式
var obj = {}; // 声明对象
obj.name = "刘德华"; // 为对象添加属性
obj.age = 55;
2.构造函数
var obj = new Object(); //使用构造函数创建对象
obj.name = "刘德华"; // 为对象添加属性
obj.age = 55;
使用Object构造函数和对象字面量来创建单个对象,采用了同一接口,会产生大量重复代码。
3.工厂模式
用函数来封装一类对象,再以特定接口(传递参数)来创建对象的细节。
function creatPerson(name,age){
var obj = new Object(); //创建一个空对象
obj.name = name; //为创建的空对象添加属性和方法
obj.age = age;
obj.sayName = function(){
alert(this.name);
}
return obj; //返回在工厂函数内创建的对象
}
var person1 = creatPerson("Alex",25);
console.log(person1.name); //Alex
var person2 = creatPerson("Oliver",18);
console.log(person2.age); //18
4.构造函数模式
可以创建自定义的构造函数,来定义自定义对象的属性和方法。
function Person(name,age){ //构造函数函数名以大写字母开头
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("Alex",25); //new 关键字创建一个对象实例,让 this 指向这个实例。
console.log(person1.name); //Alex
var person2 = new Person("Oliver",18);
console.log(person2.name); //Oliver
“构造函数”,其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new关键字,就能生成实例,并且this变量会绑定在实例对象上。
创建Person的新实例,必须使用new关键字。这种方法调用构造函数实际经历了以下过程:
(1) 创建一个空对象;
(2) 这个空对象指向原型连链接(因此 this 就指向了这个空对象);
(3) 执行构造函数中的代码(为这个空对象添加属性);
(4) 由于构造函数person没有return返回值,那么这个空对象就被返回,赋值给新对象person1。(没有retuen,默认是return this;如果构造函数person有return,就只是将return的结果给到新对象)。
实例化的过程其实就是拷贝构造函数属性和方法的过程。
构造函数模式创建的所有对象均继承自Object。所以此例中创建的所有对象既是 Object 的实例,同时也是 Person的实例。
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
使用构造函数的主要问题就是每个方法都要在每个实例上重新创建一遍。
在下一种原型模式中,将共享的方法放在原型中,那么不管创建多少个实例,这些方法不会随着创建实例而创建,节省了内存。在调用这些方法的时候,会先从实例中去找,实例中找不到,就去原型中找。
如果将这些方法写在构造函数中,实例化的时候,会拷贝构造函数中的所有属性和方法,那么每个实例都会有这些方法,浪费内存。
//person1和person2都有一个名为sayName()的方法。这两个方法不是同一个Function的实例。两个sayName()函数的作用域链是不一样的。
alert(person1.sayName === person2.sayName); //false
当然也可以通过把函数定义转移到构造函数外。person1 和 person2 对象就共享了在全局作用域中定义的同一个 sayName()函数。
function Person(name, age){
this.name = name;
this.age = age;
this.sayName = sayName; //在构造函数中引用sayName方法
}
//在全局作用域中定义sayName()方法。
function sayName(){
alert(this.name);
}
var person1 = new Person("Alex",25);
var person2 = new Person("Oliver", 18);
alert(person1.sayName === person2.sayName); //true
但这样做又污染了全局作用域。
使用new操作符实例化对象时,构造函数存在return的情况:
(1)return的是引用类型的值
如果构造函数的返回值是引用类型的值,那么使用new操作符实例化对象时,新对象将接受这个返回值。
function Person(){
this.name = 'peter';
return {
name: 'jack'
};
}
var p1 = new Person();
//返回了一个对象
//p1 = {name:"jack"}
console.log(p1.name) //jack
console.log(p1.constructor == Object); //true
console.log(p1 instanceof Object); //true
(2)return的是基本类型的值
如果构造函数返回的是基本类型的值,使用new操作符实例化对象时,新对象接受的是创建好的对象,返回值对构造函数没有影响。
function Person(){
this.name = 'peter';
return 'jack';
}
var p2 = new Person();
console.log(p2.name) //peter
console.log(p2.constructor == Person); //true
5.原型模式
prototype(原型)就是通过调用构造函数创建的那个实例的原型对象。
使用原型对象,可以让所有对象实例共享它所包含的属性和方法。可以把那些不变的属性和方法,直接定义在prototype对象上。
function Person(){}; //构造函数是一个空函数
//直接将sayName()方法和所有属性添加到原型中。
Person.prototype.name = "Alex";
Person.prototype.age = 25;
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
console.log(person1.name); //Alex
console.log(person2.name); //Alex
创建的实例对象是不包含任何属性和方法的。之后仍然可以调用person1.sayName()。是通过查找对象属性的过程来实现的,实例对象的内部属性[[Prototype]]指向函数的原型对象,而原型对象里包含有sayName()方法。
当代码读取某个对象的某个属性时,就会执行搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。
简洁版原型模式
用一个包含所有属性和方法的对象字面量来重写整个原型对象。
function Person(){};
Person.prototype = {
name: "Alex",
age: 25,
sayName: function(){
console.log(this.name);
}
};
var person1 = new Person();
console.log(person1.name);
用这样的方式完全重写了prototype对象,constructor属性就不再指向Person了,而是指向Object。
var friend = new Person();
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true
原型对象的问题
原型中所有的属性都是共享的,通过给实例添加一个同名属性,可以屏蔽掉原型中的对应(基本类型的值的)属性(只是屏蔽掉了,并不是更改了,更改只能通过原型对象本身。基本类型的值都保存在栈内存中,每个实例共享的这些属性值为基本类型的属性相互之间是独立的)。
function Person(){
}
Person.prototype = {
constructor: Person,
//重写原型对象会使实例的constructor属性由指向构造函数Person而转变为指 向Object,这里要手动定义其指向。
name : "Alex",
age : 25,
job : " Doctor",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Oliver";
console.log(person1.name); //Oliver
console.log(person2.name); //Alex
但是对于属性值为引用类型的属性来说:改变其中一个实例的属性,也将影响到其他实例的属性。这些属性都是指向的同一个保存在堆内存中的属性值。
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true
6.组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性和方法,原型模式用于定义共享的属性和方法。
function Person(name,age){
this.name = name;
this.age = age;
this.friends = ["Shelby", "Court"];
};
Person.prototype = {
sayName : function(){
console.log(this.name);
}
};
var person1 = new Person("Alex",25);
console.log(person1.sayName()); //Alex
var person2 = new Person();
person2.name = "Oliver";
console.log(person2.name); //Oliver
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
改进
工厂模式、构造函数模式、原型模式、混合模式,都可以将一个对象作为参数传递。
function Person(option){
this.name = option.name;
this.age = option.age;
this.job = option.job;
this.friends = ["Shelby", "Court"];
};
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
};
var person1 = new Person({
name: "Alex",
job: "Doctor"
});
进一步改进
在原型中定义_init函数,将原本需要在构造函数中定义的属性采用_init初始化函数定义。然后在构造函数中调用_init函数。
function Person(option){
this._init(option);
}
Person.prototype = {
_init: function(option){
this.name = option.name;
this.age = option.age;
this.job = option.job;
this.friends = ["Shelby", "Court"];
},
constructor : Person,
sayName: function(){
alert(this.name);
}
}
var person1 = new Person({
name: "Alex",
job: "Doctor"
});
7.扩充模式
首先需要自定义一个extend()函数:
/*通过拷贝扩充一个对象*/
//target--目标对象
//source--存储目标对象需要扩充的数据
//把source里面的数据全部拷贝添加给target。
function extend(target,source){
for( var key in source ){
target[key] = source[key];
};
return target ;
}
/* 应用 */
var Alex = {sex: "male"};
var boy = {name: "Alex", age: 25};
var newAlex = extend(Alex,boy);
console.log(newAlex); //{sex: "male", name: "Alex", age: 25}
把多个对象拷贝给目标对象:
// extendMany(target, obj1,obj2,obj3......)
//如果没有传入任何参数,函数停止运行;
//如果只传入一个参数,就把这个参数赋值给target,再把target返回。相当于是没有任何改变;
//如果传入多个参数,那么第一个参数是原始目标对象,把后面的对象全部拷贝给目标对象。
function extendMany(){
var len = arguments.length;
var target = null;
if(len === 0){
return;
}else if(len === 1){
target = this;
}else{
target = arguments[0];
}
for(var i = 1; i < len; i++){
for(var key in arguments[i]){
target[key] = arguments[i][key];
}
}
return target;
}
/* 应用 */
var Alex = {sex: "male"};
var boy = {name: "Alex", age: 25};
var job = {job: "Doctor"};
var newAlex = extendMany(Alex,boy,job);
console.log(newAlex); //{sex: "male", name: "Alex", age: 25, job: "Doctor"}
8.使用第三方框架
采用如下两个第三方框架创建对象,就不用同时创建构造函数和原型。
base2框架
<!-- 首先需要引入base2框架包 -->
<script type="text/javascript" src="base2.js"></script>
//constructor方法内定义属性
var Person = Base.extend({
constructor: function(name,age){
this.name = name;
this.age = age;
},
sayName: function(){
console.log(this.name);
}
});
var person1 = new Person("Alex", 25);
person1.sayName(); //Alex
simple框架
<!-- 首先需要引入simplejavascriptinheritance框架包 -->
<script type="text/javascript" src="simplejavascriptinheritance.js"></script>
//init方法内定义属性
var Person = Class.extend({
init: function(name,age){
this.name = name;
this.age = age;
},
sayName: function(){
console.log(this.name);
}
});
var person1 = new Person("Alex",25);
person1.sayName(); //Alex