js中创建对象的方式
大纲:
- 创建单个对象(Object构造函数,对象字面量)
- 创建多个对象(工厂模式,构造函数模式,原型模式,组合使用构造函数模式和原型模式,动态原型模式,寄生构造函数模式,稳妥构造函数模式)
一、创建单个对象
- 什么是对象?
无序属性的集合,其属性可以包含基本值、对象或函数
1、Object构造函数:
- 思路:
先创建一个Object的实例,然后再为它添加属性和方法
- 例子:
var fruit = new Object(); //创建Object的实例,赋值给fruit
fruit.name = "apple"; //添加属性
fruit.price = 3;
fruit.color = "red";
fruit.isName = function(){ //添加方法
alert(this.name)
}
fruit.isName() //调用方法
2、对象字面量:
- 例子:
var fruit = {
name:"apple",
price:3,
color:"red",
isName:function(){
alert(this.name)
}
}
fruit.isName()
二、创建多个对象
1、工厂模式:
- 本质:
用函数来封装以特定接口创建对象的细节
- 优点:
解决了创建多个相似对象的问题。即创建一次,多次调用
- 例子:
function myFruit(name,price,color){
var fruit = new Object();
fruit.name = name;
fruit.price = price;
fruit.color = color;
fruit.isName = function(){
alert(this.name)
};
return fruit;
}
var apple = myFruit("apple",3,"red");
var banana = myFruit("banana",2,"yellow")
console.log(apple); //apple是一个有着三个属性,一个方法的对象
- 缺点:
工厂模式不能识别对象的类型
console.log(apple instanceof Object); //true
console.log(apple instanceof myFruit); //false
- 小结:
1、工厂函数与Object构造函数写法类似,把对象内容封装在myFruit函数里,并给函数三个参数,对应fruit对象里的三个属性。然后return这个对象。
2、缺点是不能识别对象类型。即我们只知道apple是Object类型,却不能识别是否是myFruit函数创建的对象
3、于是出现了构造函数模式
2、构造函数模式:
1、除了原生构造函数(Oject,Array等),我们也可以自定义构造函数。
2、这意味着将来可以将它的实例标识为一种特定的类型
3、构造函数本身也是函数,只不过可以用来创建对象而已
- 例子:
代码里的注释列举了构造函数模式和工厂函数的不同之处
function Myfruit(name,price,color){ //1、首字母大写
this.name = name; //2、没有显示地创建对象Object
this.price = price; //3、直接将属性和方法赋值给了this对象
this.color = color;
this.isName = function(){
alert(this.name)
}; //4、没有return
}
var apple = new Myfruit("apple",3,"red"); //5、用new操作符调用
var banana = new Myfruit("banana",2,"yellow")
//构造函数模式可以识别对象类型
console.log(apple instanceof Object); //true
console.log(apple instanceof Myfruit); //true
- 注意:
要创建Myfruit的实例,必须使用new操作符。new会经历4个步骤:
1、创建一个新对象
2、将构造函数的作用域赋值给新对象(this指向这个新对象)
3、为新对象添加属性和方法
4、返回新对象
- 缺点:
不同实例上的同名函数是不相等的,即每个方法都要在每个实例上重新创建一遍。
console.log(apple.isName == banana.isName); //false
- 小结:
构造函数的优点是:创建一次,多次调用。相比工厂模式,构造函数可以识别对象类型
缺点是:每个实例上相同任务的方法不能共享
3、原型模式:
- 几个概念:
1、所有的函数都有一个prototype属性
2、prototype就是通过调用构造函数而创建的那个对象实例的原型对象
3、使用prototype可以让所有对象实例共享它的属性和方法
4、所有的原型对象都会自动获得一个constructor属性,这个属性指向prototype属性所在的构造函数。即Myfruit.prototype.constructor == Myfruit
- 特点:
所有的方法和属性都加在了构造函数的prototype属性上,构造函数是空的
- 思路:
1、创建一个空的构造函数
2、把属性和方法都放在构造函数的prototype属性上
3、此时prototype上的属性和方法都可以共享了
- 例子:
function Myfruit(){} //1、创建一个空的构造函数
Myfruit.prototype.name = "apple"; //2、属性和方法都写在构造函数的prototype属性上
Myfruit.prototype.price = 3;
Myfruit.prototype.color = "red";
Myfruit.prototype.isName = function(){
alert(this.name)
}
var apple = new Myfruit();
apple.isName() //apple
var banana = new Myfruit();
banana.isName() //apple
console.log(apple.isName === banana.isName); //true
- 简写用字面量的方式:
上面代码有许多重复的,可以用字面量方法简写
function Myfruit(){}
Myfruit.prototype={
name:"apple",
price:3,
color:"red",
isName : function(){
alert(this.name)
}
}
var apple = new Myfruit();
这种写法有个问题,就是虽然使用instanceof依然可以测出对象类型,但是原型对象里的constructor属性不再指向Myfruit构造函数,而是指向Object。本质上重写了默认的prototype对象
console.log(apple instanceof Object); //true
console.log(apple instanceof Myfruit); //true
console.log(apple.constructor === Myfruit); //false
console.log(apple.constructor === Object); //true
console.log(Myfruit.prototype.constructor === Myfruit); //false
console.log(Myfruit.prototype.constructor === Object); //true
我们可以自己手动设置constructor值为Myfruit,使得constructor指向Myfruit
function Myfruit(){}
Myfruit.prototype={
constructor:Myfruit, //手动设置constructor值
name:"apple",
price:3,
color:"red",
isName : function(){
alert(this.name)
}
}
var apple = new Myfruit();
console.log(apple.constructor === Myfruit); //true
console.log(apple.constructor === Object); //false
console.log(Myfruit.prototype.constructor === Myfruit); //true
console.log(Myfruit.prototype.constructor === Object); //false
这样还有个问题,我们自己设置的constructor属性的[[Enumerable]]特性被设置为true,也就是我们设置的constructor属性是可枚举的。而原生的是不可枚举的。
function Myfruit(){}
Myfruit.prototype={
constructor:Myfruit,
name:"apple",
price:3,
color:"red",
isName : function(){
alert(this.name)
}
}
Object.defineProperty(Myfruit.prototype,"constructor",{
enumerable:false, //设置不可枚举
value:Myfruit
})
- 字面量写法小结:
1、字面量写法简单,但原生的constructor属性却指向Object,而不是Myfruit构造函数
2、可以手动设置constructor:Myfruit
3、此时,我们设置的constructor属性是可枚举的,原生的是不可枚举的
4、可以用Object.defineProperty()方法修改[[Enumerable]]特性为false
- 注意:
1、当修改原型时,实例可以写在修改原型之前
2、但是重写原型时,实例必须写在重新原型之后,因为把原型修改为另一个对象等于切断了构造函数与最初原型之间的联系
- 缺点:
1、省略了为构造函数传递初始化参数,使得实例在默认情况下都将取得相同的属性值
2、如果添加的属性是引用类型值的属性,那么也会在实例上共享。
3、看例子。只给lion的friends数组里添加了tiger,但是cat对象的friends数组里也有,这不是我们想要的。
function Zoo(){}
Zoo.prototype={
constructor:Zoo,
name:"lion",
age:5,
color:"yellow",
friends:["wolf","giraffe"],
isName : function(){
alert(this.name)
}
}
var lion = new Zoo();
var cat = new Zoo();
lion.friends.push("tiger")
console.log(lion.friends); //["wolf","giraffe","tiger"]
console.log(cat.friends); //["wolf","giraffe","tiger"]
4、组合使用构造函数模式和原型模式
- 思路:
1、把不需要共享的属性放在构造函数里
2、把需要共享的属性和方法放在构造函数的prototype属性里
- 例子:
function Zoo(name,age,color){ //不能共享的
this.name = name;
this.age = age;
this.color = color;
this.friends = ["wolf","giraffe"]
}
Zoo.prototype={ //需要共享的
constructor:Zoo,
isName : function(){
alert(this.name)
}
}
var lion = new Zoo("lion",5,"yellow");
var cat = new Zoo("cat",2,"white");
lion.friends.push("tiger")
console.log(lion.friends); //["wolf","giraffe","tiger"]
console.log(cat.friends); //["wolf","giraffe"]
console.log(lion.friends === cat.friends); //false
console.log(lion.isName === cat.isName); //true
5、动态原型模式:
- 特点:
把独立的构造函数和原型对象都封装在一个构造函数里。然后再通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型
- 例子:
function Zoo(name,age,color){
this.name = name;
this.age = age;
this.color = color;
//方法
//判断当不存在isName方法时,初始化原型
if (typeof this.isName != "function") {
Zoo.prototype.isName = function(){ //不能使用字面量
alert(this.name)
}
}
}
var lion = new Zoo("lion",5,"yellow");
lion.isName()
6、寄生构造函数模式
- 思路:
创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象
- 例子:
假如我们想有个其他方法的特殊数组,但是又不能之间修改Array的构造函数
function Myarr (){
var arr = new Array(); //1、创建数组
arr.push.apply(arr,arguments); //2、初始化数组的值
arr.addLine = function(){ //3、添加addLine方法
return this.join("~")
};
return arr; //如果没有返回,
}
var colors = new Myarr("red","yellow","black")
console.log(colors.addLine()); //red~yellow~black
- 小结:
1、寄生构造函数很像工厂模式,不同的是,构造函数总是首字母大写,且是用new调用的。
2、返回的对象与构造函数或与构造函数的原型属性之间没有关系
3、不能使用instanceof识别对象类型
7、稳妥构造函数模式
- 稳妥对象
是指没有公共属性,其方法也不引用this的对象,不使用new调用。
下面注释的是与寄生构造函数的区别。
function Zoo (name,age){
var zoo = new Object();
zoo.isName = function(){
alert(name) //1、不使用this
}
return zoo;
}
var lion = Zoo("lion",5); //2、不使用new调用
lion.isName() //lion
- 小结:
1、稳妥构造函数和寄生构造函数很像,不同的是,稳妥构造函数不使用this,不使用new
2、返回的对象与构造函数或与构造函数的原型属性之间没有关系
3、不能使用instanceof识别对象类型
4、这个模式提供的安全性,非常适合再某些安全执行环境下使用