JS设计模式之构造函数模式1
关键词:类,实例,原型
构造函数定义:
构造函数用于创建特定类型的对象——不仅声明了使用的对象,构造函数还可以接受参数以便第一次创建对象的时候设置对象的成员值。你可以自定义自己的构造函数,然后在里面声明自定义类型对象的属性或方法。
如果你刚接触构造函数,上面的概念可以完全忽略了,因为你根本看不懂在讲什么,直接跳过就好了。
先从工厂模式开始写, 应该能更好理解构造函数。
工厂模式:
function create(name,age) {
var obj={};
obj.name=name;
obj.age=age;
obj.createJs=function () {
console.log(this.name+'和'+this.age)
}
return obj;
}
var p1=create('js',19);
p1.createJs();
如果把它改写成构造函数,就应该是这样的:
function Create(name,age) {
// var obj={};
this.name=name;
this.age=age;
this.createJs=function () {
console.log(this.name+'和'+this.age)
}
// return obj;
}
var p1=new Create('js',19);
p1.createJs();
乍一看,差别很大啊,是怎么改写的呢?
搞明白构造函数的特点,会更容易理解它们的区别。
1、函数名大写(不是规定,约定俗成的一种规范);
2、执行 new Create()
之后,Create
就有了一个新的名字:类(它依然是函数,也要形成一个私有作用域,形参赋值,预解释,代码从上到下执行)
它的特点(与普通函数不同的地方)是:在代码执行之前,不用自己手动创建对象obj
了,所以看不到 obj
,浏览器会默认创建一个对象数据类型值(这个对象相当于构造函数中的obj),也就是函数中的this。
3、工厂模式里返回值是一个对象 p1,那么构造函数里的返回值呢?
构造函数里的代码从上到下执行,默认地把创建的实例 p1 作为了返回值。并且以当前的实例(p1)为执行的主体,把属性名和属性值赋给当前的实例 p1。
如果再创造另一个实例 var p2=new CreateJs('js',19)
;那么 p1 和 p2 有什么关系,它们是独立的,没有任何关系。p1==p2 //false;
上面的函数继续改写
function Create(name,age) {
var name='xiaoming';
this.name=name;
this.age=age;
this.createJs=function () {
console.log(this.name+'和'+this.age)
}
}
var p1=new Create('js',19);
上面只增加了一行代码,那么增加的一行代码和实例有什么关系呢?答案是没有任何关系,该如何理解呢?
先从概念上解析这句话,构造函数执行后返回的是一个实例(对象),构造函数把** 实例相关( this ) **的属性和属性值对实例进行赋值(别忘了 浏览器事先创建了一个obj
,最后把这个obj
返回出去变成了p1), var name='xiaoming'
和obj没有任何关系,它是构造函数内部私有变量。
继续改写代码
function Create(name,age) {
var name='xiaoming';
this.name=name;
this.age=age;
this.createJs=function () {
console.log(this.name+'和'+this.age)
}
return name;
// return function (){ console.log(11)};
}
var p1=new Create('js',19);
console.log(p1); //Create { name: 'xiaoming', age: 19, createJs: [Function] }
//console.log(p1);//结果是手动返回的函数
构造函数执行后,默认返回一个对象。如果手动添加一个基本类型返回值,它的返回值依然是浏览器添加的默认返回值;如果手动添加的返回值是一个引用数据类型,那么它默认的返回值就会被覆盖掉;所以最好不要给构造函数添加返回值。
既然有了工厂模式,为什么还要设计构造函数模式呢?
/*工厂模式*/
function create(name, age) {
var obj={}
obj.name=name;
obj.age=age;
obj.createJs=function () {
console.log(this.name+'和'+this.age)
}
return obj;
}
var p1=create('js',18);
var p2=create('jss',19);
console.log(p1==p2)//false(完全不同的内存地址)
/*构造函数模式*/
function Create(name, age) {
this.name=name;
this.age=age;
this.createJs=function () {
console.log(this.name+'和'+this.age)
}
}
var p11=new Create('js',18);
var p22=new Create('jss',19);
console.log(p11==p22)//false
他们相似的地方是,每一个实例对象,都有自己的属性和方法的副本,每个实例都会占用不同的内存空间。但是,如果每个实例上的** 共同属性(不变的属性) **都写在构造函数(类)内部,那么每创造一个实例,就会把所有相关的属性(包括共同属性)拷贝到对应实例的内存空间下,这是极大的资源浪费。例如:
function Create(name, age) {
this.name=name;
this.age=age;
this.chara=function(){ console.log('我们是公有属性') };
this.createJs=function () {
console.log(this.name+'和'+this.age)
}
}
var p11=new Create('js',18);
var p22=new Create('js',18);
console.log(p11.chara==p22.chara);//false
this.chara
是每个实例共同拥有的方法,每次实例化就会拷贝到自对应实例的内存空间下,造成了极大的浪费,这是new方法的缺陷。
构造函数为了解决这个问题,引入了原型prototype
,prototype
是一个对象,是用来存储实例的公有属性和方法的。
Create.prototype.chara=function () {
console.log('share')
}
console.log(p11.chara) //function
console.log(p11.chara==p22.chara);//true
实例一旦创建,将自动引用prototype
对象的属性和方法。也就是说,实例对象的属性和方法,分成了两种,一种是本地的(私有的),另一种是引用的(公有的)。
现在,chara
属性放在prototype
对象里,是两个实例对象共享的。只要修改了prototype
对象,就会同时影响到两个实例对象。
检测实例是否属于类
console.log(p1 instanceof Create); //true;
var arr=[];
console.log(arr instanceof Array); //true;